#include "server.h"

//  modify on 3.2   add  tcp keepalive 

void *
INETADDR_ADDRESS(const struct sockaddr * a)
{
    if (a->sa_family == AF_INET6)
    {
        return &((struct sockaddr_in6 *)a)->sin6_addr;
    }
    else
    {
        return &((struct sockaddr_in *)a)->sin_addr;
    }
}

void
print_hex(char *buffer, int size)
{
    unsigned char c;
    int p = 0;
    for (p = 0; p < size; p++)
    {
        c = *(buffer + p);
        //	sprintf(i, "%2.2x", buffer + p);
        printf("%2.2x", c);
    }
    printf("\n");
}

char *
get_rad_var(const char *name, const char *buf, size_t buf_len)
{
    const char	*p, *e, *s;
    char		*val;
    size_t		var_len, len;

    var_len = strlen(name);
    e = buf + buf_len;
    val = NULL;

    /* buf is "var1=val1&var2=val2...". Find variable first */
    for (p = buf; p + var_len < e; p++)
        if ((p == buf || p[-1] == '&') && p[var_len] == '=' &&
                !strncmp(name, p, var_len))
        {

            /* Point p to variable value */
            p += var_len + 1;

            /* Point s to the end of the value */
            s = (const char *) memchr(p, '&', e - p);
            if (s == NULL)
                s = e;

            /* Try to allocate the buffer */
            len = s - p;
            if ((val = (char *) malloc(len + 1)) != NULL)
                memcpy(val, p, len);
            val[len] = 0;
            break;
        }

    return (val);
}

int udp_packet_calculate_md5(char *packet_buffer, unsigned int packet_size, char *secret, unsigned int secret_length, unsigned char *md5_out)
{
    unsigned char *temp;
    temp = malloc(packet_size + secret_length);
    if (!temp)
    {
        return -1;
    }
    memcpy(temp, packet_buffer, packet_size);
    memcpy(temp + packet_size, secret, secret_length);
    fr_md5_calc(md5_out, temp, packet_size + secret_length);
    free(temp);
    return 0;
}

int table_chain_init()
{
    unsigned int i;

    memset(table_chain, 0 , sizeof(struct table_chain) * MAX_TABLE_NUMBER);
    free_table_header = table_chain;
    table_chain[0].prev = NULL;
    table_chain[0].next = (MAX_TABLE_NUMBER > 1) ? &table_chain[1] : NULL;
    table_chain[0].id = 0;
    table_chain[0].type = TABLE_TYPE_FREE;
    for (i = 1; i < MAX_TABLE_NUMBER - 1; i++)
    {
        table_chain[i].prev = &table_chain[i - 1];
        table_chain[i].next = &table_chain[i + 1];
        table_chain[i].id = i;
        table_chain[i].type = TABLE_TYPE_FREE;
    }
    i = MAX_TABLE_NUMBER - 1;
    free_table_tail = &table_chain[i];
    table_chain[i].prev = (MAX_TABLE_NUMBER > 1) ? &table_chain[i - 1] : NULL;
    table_chain[i].next = NULL;
    table_chain[i].id = i;
    table_chain[i].type = TABLE_TYPE_FREE;
    return 1;
}

struct table_chain *
find_free_table()
{
    struct table_chain *table;
    table = free_table_header;
    if (!free_table_header) return NULL;
    if (free_table_header == free_table_tail)
    {
        free_table_header = NULL;
        free_table_tail = NULL;
        return table;
    }
    else
    {
        table->next->prev = NULL;
        free_table_header = table->next;
        return table;
    }
}

int add_table (struct table_chain *table, TYPE type)
{
    struct table_chain **chain_header;
    if (!table) return 0;
    if (table->type == type) return 0;
    switch (type)
    {
    case TABLE_TYPE_ACCT:
        chain_header = &acct_table_header;
        break;
    case TABLE_TYPE_AUTH:
        chain_header = &auth_table_header;
        break;
    case TABLE_TYPE_FREE:
        return 0;
    default:
        return 0;
    }
    if (*chain_header) (*chain_header)->prev = table;
    table->prev = NULL;
    table->next = *chain_header;
    *chain_header = table;
    table->type = type;
    return 1;
}

int del_table (struct table_chain *table)
{
    struct table_chain **chain_header;
    if (!table) return 0;
    switch (table->type)
    {
    case TABLE_TYPE_ACCT:
        chain_header = &acct_table_header;
        break;
    case TABLE_TYPE_AUTH:
        chain_header = &auth_table_header;
        break;
    case TABLE_TYPE_FREE:
        return 0;
    default:
        return 0;
    }
    if (*chain_header == table)
    {
        *chain_header = table->next;
    }
    if (table->next) table->next->prev = table->prev;
    if (table->prev) table->prev->next = table->next;
    return 1;
}

int free_table  (struct table_chain *table)
{
    if (del_table(table) != 1) return 0;
    if (table->table) free(table->table);
    table->type = TABLE_TYPE_FREE;
    if (free_table_header == NULL)
    {
        table->next = NULL;
        table->prev = NULL;
        free_table_header = table;
        free_table_tail = table;
    }
    else
    {
        free_table_tail->next = table;
        table->prev = free_table_tail;
        table->next = NULL;
        free_table_tail = table;
    }
    return 1;
}


// call once a ACCOUNTING_CHECK_TIME
int user_timeout_callback(struct table_chain *table)
{
    if (table->type == TABLE_TYPE_ACCT)
    {
        // printf("check user %s(%s) in table %d,who update %d seconds before.\n", table->table->name, (table->table->status == USER_ACCOUNTING)? "accounting":"exit",
        // table->id, (int)(time(NULL) - table->table->update_time));
        if ((time(NULL) - table->table->update_time) > WAIT_RELOGIN_TIME)
        {
            if (table->table->status == USER_ACCOUNTING)
            {
                Vdebug(DEBUG_DATE, "[timer]user %s, did not update within %d seconds, accounting off.", 
					table->table->name, USER_OFFLINE_TIME);
                rad_account_packet_send(table, ACCT_STOP, "Session-Timeout", NULL);
            }
            if ((table->table->status == USER_EXIT) || (table->table->status == USER_KICKOFF))
            {
                //  printf("remove table of user %s\n", table->table->name);
            }
            free_table(table);
            return 1;   // delete timer
        }
        else if (table->table->status == USER_ACCOUNTING)
        {
            if (table->table->count == ACCOUTING_UPDATE_TIMES)
            {
                rad_account_packet_send(table, ACCT_ALIVE, NULL, NULL);
                table->table->count = 0;
            }
            else
            {
                table->table->count++;
            }
        }
    }
    else
    {
        return 1;
    }
    return 0;    //continue  timing
}

#if USE_SSL_AUTHENTICATION
pthread_t pthreads_thread_id(void)
{
    pthread_t ret;
    ret=pthread_self();
    return(ret);
}

void pthreads_locking_callback(int mode, int type, char *file,
                               int line)
{
    if (mode & CRYPTO_LOCK)
    {
        pthread_mutex_lock(&(lock_cs[type]));
        lock_count[type]++;
    }
    else
    {
        pthread_mutex_unlock(&(lock_cs[type]));
    }
}
#endif

int password_callback(char *buf, int size, int rwflag, void *userdata)
{
    printf("*** Callback function called\n");
    strcpy(buf, "777777");
    return 6;
}

struct table_chain *new_table_init(char *name, char *pass, char *ipaddr, int type, char *v)
{
    struct table_chain *chain;
    struct nas_user_table *table;
    unsigned int ld = 0;
    char session_tx[SESSION_TX_LENGTH + 1] = {0};
    char secret[17] = {0};
    chain = find_free_table();
    if (!chain) return NULL;
    add_table(chain, TABLE_TYPE_AUTH);
    //Generate session ID
    ld = chain->id;
    sprintf(session_tx, "%8.8x", ld);
    ld = time(NULL);
    sprintf(secret, "%8.8x", ld);    // keep current time as a secret between sever and client
    memcpy(session_tx + 8, secret, 8);
    table = malloc(sizeof(struct nas_user_table));
    memset(table , 0, sizeof(struct nas_user_table));
    chain->table = table;

    table->login_type = type;
    memcpy(table->secret, secret, UDP_PACKET_SECRET_KEY_LENGTH);
    memcpy(table->name, name, strlen(name));
	memcpy(table->pass, pass, strlen(pass));
    memcpy(table->ipaddr, ipaddr, 40);
    memcpy(table->session_tx, session_tx, SESSION_TX_LENGTH);
    memcpy(table->version, v, strlen(v));
    table->packet_id = 0;
    table->login_time = time(NULL);
    table->update_time = time(NULL);
    table->status = USER_AUTHENTICATING;
    return chain;
}

int user_start_account(struct table_chain *chain)
{
    del_table(chain);
    add_table(chain, TABLE_TYPE_ACCT);
    chain->table->status = USER_ACCOUNTING;
    count = (++count > ACCOUTING_UPDATE_TIMES) ? 0 : count;
    chain->table->count = count;
    chain->table->timer = add_timer(&queue, chain);
    return 1;
}

int ssl_accept_user(SSL *ssl, char *name, char *pass, char *mac, int *version, int *login_type, 
					struct login_request_client_200 *client200)
{
    int ret, p, namelen;
    char receive[512] = {0}, *s;
    struct login_request_client *client;

    client = (struct login_request_client *)receive;
    if ((ret = SSL_accept(ssl)) <= 0)
    {
        Vdebug(DEBUG_BOTH, "Handshake failed: %d", SSL_get_error(ssl, ret));
        //  ERR_print_errors_fp(stdout);
        return -1;
    }
	if (ret == 2) return 2;   //  client  keepalive tcp packet
    if ((p = SSL_read(ssl, receive, 512)) <= 0)
        return -1;
    if (p < sizeof(struct login_request_client))
    {
        Vdebug(DEBUG_BOTH, "error user name pass packet length");
        return -1;
    }
	if (p >= sizeof(struct login_request_client_200))
		memcpy(client200, receive, sizeof(struct login_request_client_200));
    memcpy(name, client->username, USER_NAME_LENGTH);
    name[USER_NAME_LENGTH] = '\0';
    memcpy(pass, client->password, USER_PASS_LENGTH);
    pass[USER_PASS_LENGTH] = '\0';
    memcpy(mac, client->mac, USER_MAC_LENGTH);
    mac[USER_MAC_LENGTH] = '\0';
    *version = client->version;
    *login_type = client->type;
    // delete the space after the user name
    namelen = strlen(name);
    while (name[--namelen] ==' ')
    {
        name[namelen] = '\0';
    }
    s = name;
    while(*s != '\0')
    {
        if(*s >='A' && *s <='Z')
            *s += 32;
        s++;
    }
    return 1;
}

int auth_user_check(char *name)
{
    struct table_chain *chain;
    for (chain = auth_table_header; chain; chain = chain->next)
    {
        if (strncmp(name, chain->table->name, USER_NAME_LENGTH) == 0)
        {
			return 1;
        }
    }
    return 0;
}
struct table_chain *acct_user_check(char *name, char *secret)
{
    struct table_chain *chain;
    for (chain = acct_table_header; chain; chain = chain->next)
    {
        if (strncmp(name, chain->table->name, USER_NAME_LENGTH) == 0)
        {
			if (*secret != 0) 
			{
				if (strncmp(secret, chain->table->secret, UDP_PACKET_SECRET_KEY_LENGTH) == 0) 
				 return chain;
			}
			else return chain;
        }
    }
    return 0;
}
int client_open_socket(char *hostname, int family, char *port, int socktype)
{
    struct addrinfo Hints, *AddrInfo;
    int RetVal, ServSock;

    memset(&Hints, 0, sizeof (Hints));
    Hints.ai_family = family;
    Hints.ai_socktype = socktype;
    Hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
    RetVal = getaddrinfo(hostname, port, &Hints, &AddrInfo);
    if (RetVal != 0)
    {
        Vdebug(DEBUG_MAIN, "[socket]getaddrinfo failed with error %d: %s",
                RetVal, gai_strerror(RetVal));
        return -1;
    }
    ServSock = socket(AddrInfo->ai_family, AddrInfo->ai_socktype, AddrInfo->ai_protocol);
    if (ServSock == -1)
    {
        Vdebug(DEBUG_MAIN, "[socket]socket() failed with error");
        return -1;
    }
    freeaddrinfo(AddrInfo);
    return ServSock;
}

int open_socket(int family, char *port, int socktype)
{
    struct addrinfo Hints, *AddrInfo;
    int RetVal, ServSock;
    int cflag = 1;

    memset(&Hints, 0, sizeof (Hints));
    Hints.ai_family = family;
    Hints.ai_socktype = socktype;
    Hints.ai_flags = AI_NUMERICHOST | AI_PASSIVE;
    RetVal = getaddrinfo(NULL, port, &Hints, &AddrInfo);
    if (RetVal != 0)
    {
        Vdebug(DEBUG_MAIN, "[socket]getaddrinfo failed with error %d: %s",
                RetVal, gai_strerror(RetVal));
        return -1;
    }
    ServSock = socket(AddrInfo->ai_family, AddrInfo->ai_socktype, AddrInfo->ai_protocol);
    if (ServSock == -1)
    {
        Vdebug(DEBUG_MAIN, "[socket]socket() failed with error");
        return -1;
    }
    setsockopt(ServSock, SOL_SOCKET,SO_REUSEADDR,(char *)&cflag,sizeof(cflag));
    
if (family == PF_INET6)
    {
        int on = 0;
        int result;
        result = setsockopt(ServSock, IPPROTO_IPV6, IPV6_V6ONLY,
                                   (char *)&on, sizeof(on));
    }
        setsockopt(ServSock, SOL_SOCKET,SO_REUSEADDR,(char *)&cflag,sizeof(cflag));

    if (bind(ServSock, AddrInfo->ai_addr, AddrInfo->ai_addrlen) == -1)
    {
        Vdebug(DEBUG_MAIN, "[socket]bind() failed with error");
        return -1;
    }
    Vdebug(DEBUG_MAIN, "[socket]'Listening' on port %s, protocol %s, protocol family %s",
           port, (AddrInfo->ai_socktype == SOCK_STREAM) ? "TCP" : "UDP",
           (AddrInfo->ai_family == PF_INET) ? "PF_INET" : "PF_INET6");
    freeaddrinfo(AddrInfo);

    if (socktype == SOCK_STREAM)
    {
        if (listen(ServSock, 5) == -1)
        {
            close(ServSock);
            Vdebug(DEBUG_MAIN, "[socket]listen() failed with error");
            return -1;
        }
    }
    return ServSock;
}

int verify_packet(char *packet_buffer, int packet_length,
                  unsigned int *packet_id, unsigned int *table_id, PACKET_TYPE *type)
{
    int id;
    unsigned char md5_in[30] = {0},  md5_out[16] = {0}, md5_value[16] = {0};

    if (packet_length != 33)
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Bad packet length : %d. Discard it." ,packet_length);
        return -1;
    }
    *type = packet_buffer[0];
    if ((*type != CLIENT_EXIT) && (*type != CLIENT_KEEPALIVE) && (*type != CLIENT_QUERY))
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Error packet id %d. Discard it.", *type);
        return -1;
    }
    sscanf(packet_buffer + 1, "%8x", packet_id);
    sscanf(packet_buffer + 9, "%8x", table_id);
    id = *table_id;
    memcpy(md5_value, packet_buffer + 17, 16);

    if (*table_id >= MAX_TABLE_NUMBER) 
    {
        Vdebug(DEBUG_DATE, "[udp packet]Error packet table id %d. Discard it.", *table_id);
        return -1;
    }
    if (table_chain[id].type != TABLE_TYPE_ACCT)
    {
        Vdebug(DEBUG_DATE, "[udp packet]Error packet table id %d: table not accounting. Discard it.", *table_id);
        return -1;
    }
    //  old packet
    if ((*packet_id == table_chain[id].table->packet_id - 1) && table_chain[id].table->last_reply[0])
    {
        if (memcmp(table_chain[id].table->last_md5, md5_value, 16))
        {
            Vdebug(DEBUG_BOTH, "[udp packet]Error resend packet md5 check. Discard it.");
            return -1;
        }
        return 2;
    }
    else if (*packet_id != table_chain[id].table->packet_id)
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Packet id not equal to table id. Discard it.");
        return -1;
    }
    //calculate md5 of client
    memcpy(md5_in, packet_buffer, 17);
    memcpy(md5_in + 17, table_chain[id].table->secret, 8);
    fr_md5_calc(md5_out, md5_in, 25);
    if (memcmp(md5_out, md5_value, 16))
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Error packet md5 check. Discard it.");
        return -1;
    }
    memcpy(table_chain[id].table->last_md5, md5_out, 16);   //keep the md5 for resend check
    return 1;
}

int parse_client_packet(char *packet_buffer, int packet_length, struct udp_packet_keepalive_client ** client)
{
    int id;
    unsigned char md5_out[MD5_LENGTH] = {0}, md5_value[MD5_LENGTH] = {0};
    char type;
    unsigned int packet_id;


    //print_hex(packet_buffer, packet_length);
    /* check packet length */
    if (packet_length < sizeof(struct udp_packet_keepalive_client) + MD5_LENGTH)
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Bad packet length : %d. Discard it." ,packet_length);
        return -1;
    }
    *client = (struct udp_packet_keepalive_client *)packet_buffer;
    type = (*client)->type;
    packet_id = (*client)->packet_id;
    id = (*client)->table_id;
    memcpy(md5_value, packet_buffer + sizeof(struct udp_packet_keepalive_client), MD5_LENGTH);
    /* check packet type */
    if ((type != CLIENT_EXIT) && (type != CLIENT_KEEPALIVE) && (type != CLIENT_QUERY))
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Error packet id %d.Discard it.", type);
        return -1;
    }
    /* check table id */

    if (id >= MAX_TABLE_NUMBER) 
    {
        Vdebug(DEBUG_DATE, "[udp packet]Error packet table id %d. Discard it.", id);
        return -1;
    }
    if (table_chain[id].type != TABLE_TYPE_ACCT)
    {
        Vdebug(DEBUG_DATE, "[udp packet]Error packet table id %d: table not accounting. Discard it.", id);
        return -1;
    }
    /* check packet id */
    if ((packet_id == table_chain[id].table->packet_id - 1) && (table_chain[id].table->last_reply_length > 0))  //old packet
    {
        if (memcmp(table_chain[id].table->last_md5, md5_value, MD5_LENGTH))
        {
            Vdebug(DEBUG_BOTH, "[udp packet]Error resend packet md5 check. Discard it.");
            return -1;
        }
        return 2;
    }
    else if (packet_id != table_chain[id].table->packet_id)
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Packet id not equal to table. Discard it.");
        return -1;
    }
    //calculate md5 of client
    if (udp_packet_calculate_md5(packet_buffer, sizeof(struct udp_packet_keepalive_client), table_chain[id].table->secret, UDP_PACKET_SECRET_KEY_LENGTH, md5_out))
    {
        Vdebug(DEBUG_BOTH, "[udp packet]error caculate md5");
        return -1;
    }
    if (memcmp(md5_out, md5_value, MD5_LENGTH))
    {
        Vdebug(DEBUG_BOTH, "[udp packet]Error packet md5 check. Discard it.");
        return -1;
    }
    memcpy(table_chain[id].table->last_md5, md5_out, MD5_LENGTH);   //keep the md5 for resend check
    return 1;
}

ssize_t ifsendto(int socket, const void *message, size_t length,
       int flags, const struct sockaddr *dest_addr,
       socklen_t dest_len)
{
	int RetVal;
	if (dest_len == 0) 
	{
		memcpy(&socket, dest_addr, sizeof(socket));
		RetVal = AES_write(socket, message, length);
		close(socket);	
		return RetVal;
	}
	else 
		return sendto(socket, message, length, flags, dest_addr, dest_len);
}

void udp_packet_process()
{
    int RetVal;
    //unsigned int FromLen;
    char ipaddr[40], reply[UDP_PACKET_MAX_LENGTH];
    int reply_length;
    struct sockaddr_storage *From;
    PACKET_TYPE packet_type;
    unsigned int id, packet_id;
    struct udp_query_queue *queue = NULL;

    struct udp_packet_keepalive_client *client;
    //struct udp_packet_keepalive_server *server;

    //FromLen = sizeof(struct sockaddr_storage);
    while (1)
    {
        if (queue)
        {
            free(queue->buf);
            free(queue);
        }
        pthread_mutex_lock(&udp_mutex);
        while (head == NULL)
        {
            pthread_cond_wait(&udp_cond, &udp_mutex);
            continue;
        }
        //	queue_number--;
        queue = head;
        head = head->next;
        if (!head)
        {
            tail = NULL;
        }
        pthread_mutex_unlock(&udp_mutex);

        From = &queue->From;
        print_ipaddr(ipaddr, *From);

        memset(reply, 0 ,sizeof(reply));

        pthread_mutex_lock(&tablemutex);
		//MLog("Lock: udp packet process");
        // RetVal = verify_packet(queue->buf, queue->packet_len, &packet_id, &id, &packet_type);
        RetVal = parse_client_packet(queue->buf, queue->packet_len, &client);
        if (RetVal == -1)  //bad packet
        {
            pthread_mutex_unlock(&tablemutex);
			//MLog("UnLock: udp packet process");
            Vdebug(DEBUG_BOTH, "[udp thread]Bad packet from %s", ipaddr);
            continue;
        }

        packet_id = client->packet_id;
        id = client->table_id;
        packet_type = client->type;

        if (RetVal == 2)   //old packet , resend
        {
            pthread_mutex_unlock(&tablemutex);
			//MLog("UnLock: udp packet process");
            Vdebug(DEBUG_DATE, "[udp thread]Resend packet to %s", ipaddr);
			RetVal = ifsendto(Serv_UDP_Sock, table_chain[id].table->last_reply, table_chain[id].table->last_reply_length, 0,
                            (struct sockaddr *)From, queue->FromLen);
            continue;
        }
        /* update traffic info */
        table_chain[id].table->instant_up_rate = client->instant_up_rate;
        table_chain[id].table->instant_down_rate = client->instant_down_rate;
        table_chain[id].table->total_up_traffic = MAX(client->total_up_traffic, table_chain[id].table->total_up_traffic);
        table_chain[id].table->total_down_traffic = MAX(client->total_down_traffic, table_chain[id].table->total_down_traffic);
        reply_length = sizeof(reply);
        switch (packet_type)
        {
        case CLIENT_QUERY:
			pthread_mutex_unlock(&tablemutex);
			//MLog("UnLock: udp packet process");
            break;
        case CLIENT_KEEPALIVE:     //  Client keep alive packet
            if (table_chain[id].table->status == USER_ACCOUNTING)
            {
                //printf("Receive keep alive packet(id:%d) from %s, found user %s in table %d\n", packet_id, ipaddr, table_chain[id].table->name, id);
                if ((time(NULL) - table_chain[id].table->update_time) <= USER_UPDATE_MIN_TIME)
                {
                    Vdebug(DEBUG_BOTH, "[udp thread]User %s update too often, discard the packet.\n", table_chain[id].table->name);
                    pthread_mutex_unlock(&tablemutex);
					//MLog("UnLock: udp packet process");
                    continue;
                }
                table_chain[id].table->update_time = time(NULL);
                table_chain[id].table->packet_id++;
                if ((table_chain[id].table->set_download_rate) || (table_chain[id].table->set_upload_rate))
                {
                    generate_server_reply(SERVER_SETRATE, id, reply, &reply_length);
                }
                else
                {
                    generate_server_reply(SERVER_KEEPALIVE, id, reply, &reply_length);
                }
                pthread_mutex_unlock(&tablemutex);
				//MLog("UnLock: udp packet process");
                RetVal = ifsendto(Serv_UDP_Sock, reply, reply_length, 0, (struct sockaddr *)From, queue->FromLen);
                if (RetVal == -1)
                {
                    Vdebug(DEBUG_BOTH, "[udp thread]sendto() failed with error");
                }
            }
            else if (table_chain[id].table->status == USER_KICKOFF)
            {
                /* Do not update time so the table will be removed soon  */
                table_chain[id].table->packet_id++;
                generate_server_reply(SERVER_KICKOFF, id, reply, &reply_length);
                pthread_mutex_unlock(&tablemutex);
				//MLog("UnLock: udp packet process");
                RetVal = ifsendto(Serv_UDP_Sock, reply, reply_length, 0, (struct sockaddr *)From, queue->FromLen);
                if (RetVal == -1)
                {
                   Vdebug(DEBUG_BOTH, "[udp thread]sendto() failed with error");
                }
            }
            else if (table_chain[id].table->status == USER_EXIT)
            {
                pthread_mutex_unlock(&tablemutex);
				//MLog("UnLock: udp packet process");
                Vdebug(DEBUG_DATE, "[udp thread]User is exiting, discard this packet.");
            }
			else {
				pthread_mutex_unlock(&tablemutex);
				//MLog("UnLock: udp packet process");
			}
            break;
        case CLIENT_EXIT:          //client exit packet
            if (table_chain[id].table->status == USER_ACCOUNTING)
            {
                //Vdebug(DEBUG_DATE, "[udp thread]%s, from %s accounting off", table_chain[id].table->name, ipaddr);
                DLog("[udp thread]%s, from %s accounting off", table_chain[id].table->name, ipaddr);
                table_chain[id].table->update_time = time(NULL);
                table_chain[id].table->status = USER_EXIT;
                table_chain[id].table->packet_id++;
                generate_server_reply(SERVER_EXIT, id, reply, &reply_length);
                rad_account_packet_send(&table_chain[id], ACCT_STOP, "User-Request", NULL);
                pthread_mutex_unlock(&tablemutex);
				//MLog("UnLock: udp packet process");
                RetVal = ifsendto(Serv_UDP_Sock, reply, reply_length, 0,
                                (struct sockaddr *)From, queue->FromLen);
                if (RetVal == -1)
                {
                    Vdebug(DEBUG_BOTH, "[udp thread]sendto() failed with error");
                }
            }
			else pthread_mutex_unlock(&tablemutex);
            break;
        default:
			pthread_mutex_unlock(&tablemutex);
			//MLog("UnLock: udp packet process");
            break;
        }
    }
}

int ssl_send_server_reply(SSL *ssl, struct login_reply_server *server, const char *reply_msg)
{
    //char ServerReply[UDP_PACKET_MAX_LENGTH] = {0};

    server->msg_length = MIN(strlen(reply_msg), 
			UDP_PACKET_MAX_LENGTH - sizeof(struct login_reply_server));
    memcpy((char *)server + sizeof(struct login_reply_server), reply_msg, server->msg_length);
    //print_hex((char *)server, sizeof(struct login_reply_server) + server->msg_length);
    return SSL_write(ssl, (char *)server, sizeof(struct login_reply_server) + server->msg_length);
}

int generate_server_reply(PACKET_TYPE type, unsigned int id, char *reply, int *reply_length)
{
    struct udp_packet_keepalive_server *server;
    unsigned char md5_out[16] = {0};
    unsigned int size;
    const char server_reply_kickoff[] = "You are kicked off";

    server = (struct udp_packet_keepalive_server *)reply;
    server->type = type;
    server->packet_id = table_chain[id].table->packet_id;
    /*  set upload download rate  */
    if (table_chain[id].table->set_upload_rate)
    {
        server->up_rate = table_chain[id].table->set_upload_rate;
        table_chain[id].table->set_upload_rate = 0;
    }
    if (table_chain[id].table->set_download_rate)
    {
        server->down_rate = table_chain[id].table->set_download_rate;
        table_chain[id].table->set_download_rate = 0;
    }
    /* set reply message */
    if (table_chain[id].table->set_message_length)
    {
        server->msg_length = MIN(*reply_length - sizeof(struct udp_packet_keepalive_server) - MD5_LENGTH, table_chain[id].table->set_message_length);
        memcpy(reply + sizeof(struct udp_packet_keepalive_server), table_chain[id].table->set_message, server->msg_length);
        Vdebug(DEBUG_DATE, "[udp thread]Send message to client:%s", table_chain[id].table->set_message);
        table_chain[id].table->set_message_length = 0;
    }
    else if (type == SERVER_KICKOFF)
    {
        server->msg_length = MIN(strlen(server_reply_kickoff), *reply_length - sizeof(struct udp_packet_keepalive_server) - MD5_LENGTH);
        memcpy(reply + sizeof(struct udp_packet_keepalive_server), server_reply_kickoff, server->msg_length);
    }
    // generate server md5
    udp_packet_calculate_md5((char *)server, sizeof(struct udp_packet_keepalive_server) + server->msg_length, table_chain[id].table->secret, UDP_PACKET_SECRET_KEY_LENGTH, md5_out);
    memcpy((char *)server + sizeof(struct udp_packet_keepalive_server) + server->msg_length, md5_out, MD5_LENGTH);

    size = sizeof(struct udp_packet_keepalive_server) + server->msg_length + MD5_LENGTH;
    memcpy(table_chain[id].table->last_reply, reply, size);                    //keep the reply for resending
    table_chain[id].table->last_reply_length = size;
    *reply_length = size;
    return 0;
}

#if USE_SSL_AUTHENTICATION
SSL_CTX * ssl_init()
{
    SSL_CTX *ctx;
    int i;

    int (*callback)(char *, int, int, void *) = &password_callback;

    printf("Secure Programming with the OpenSSL API, Part 4:\n");
    printf("Serving it up in a secure manner\n\n");

    SSLeay_add_ssl_algorithms();
    SSL_load_error_strings();
    ERR_load_BIO_strings();
    ERR_load_SSL_strings();
    OpenSSL_add_all_algorithms();

    printf("Attempting to create SSL context... ");
    ctx = SSL_CTX_new(SSLv23_server_method());
    if (ctx == NULL)
    {
        printf("Failed. Aborting.\n");
        return 0;
    }

    printf("\nLoading certificates...\n");
    SSL_CTX_set_default_passwd_cb(ctx, callback);

    if (!SSL_CTX_use_certificate_file(ctx, "usercert.pem", SSL_FILETYPE_PEM))
    {
        ERR_print_errors_fp(stdout);
        SSL_CTX_free(ctx);
        return 0;
    }
    if (!SSL_CTX_use_PrivateKey_file(ctx, "userkey.pem", SSL_FILETYPE_PEM))
    {
        ERR_print_errors_fp(stdout);
        SSL_CTX_free(ctx);
        return 0;
    }
    /* Set the openssl callback for multi threads */
    lock_cs=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
    lock_count=OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
    for (i=0; i<CRYPTO_num_locks(); i++)
    {
        lock_count[i]=0;
        pthread_mutex_init(&(lock_cs[i]),NULL);
    }
    CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
    CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
    return ctx;
}
#endif

int admin_auth(char *reply)
{
    struct table_chain *table;
    int n = 0;

    pthread_mutex_lock(&tablemutex);
    for (table = auth_table_header; table; table = table->next)
    {
        n++;
        if (n > MAX_TABLE_NUMBER)
        {
            sprintf(reply, "Some error happen");
            MLog("Fatal error: auth table number exceed");
            return 0;
        }
    }
    pthread_mutex_unlock(&tablemutex);
    sprintf(reply, "Authenticating user number : %d", n);
    Vdebug(DEBUG_DATE, "[admin]%s", reply);
    return 0;
}

int admin_acct(char *reply)
{
    struct table_chain *table;
    int n = 0;

    pthread_mutex_lock(&tablemutex);
    for (table = acct_table_header; table; table = table->next)
    {
        n++;
        if (n > MAX_TABLE_NUMBER)
        {
            sprintf(reply, "Some error happen");
            MLog("Fatal error: acct table number exceed");
            return 0;
        }
    }
    pthread_mutex_unlock(&tablemutex);
    sprintf(reply, "Accounting user number : %d", n);
    Vdebug(DEBUG_DATE, "[admin]%s", reply);
    return 0;
}

int admin_checkuser(char *reply, char *receive)
{
    struct table_chain *table;
    int n = 0;
    char name[USER_NAME_LENGTH + 1] = {0}, sessionid[SESSION_TX_LENGTH + 1] = {0};
    int checkname, checkid;

    checkname = (copy_message(name, USER_NAME_LENGTH + 1, receive, '"') > 0) ? 1 : 0;
    checkid = (copy_message(sessionid, SESSION_TX_LENGTH + 1, receive, '\'') > 0) ? 1 : 0;

    if ((!checkname) && (!checkid))
    {
        sprintf(reply, "Error checkuser format, use \" to enclose the user name and ' to enclose session id.");
        Vdebug(DEBUG_DATE, "[admin]%s", reply);
        return 0;
    }
    pthread_mutex_lock(&tablemutex);
    for (table = acct_table_header; table; table = table->next)
    {
        //assert(table->table);
        if (((checkname && strncmp(table->table->name, name, MAX(strlen(table->table->name), strlen(name)))) == 0)
                && ((checkid && strncmp(table->table->session_tx, sessionid, MAX(strlen(table->table->session_tx), strlen(sessionid)))) == 0))
        {
            n++;
        }
    }
    pthread_mutex_unlock(&tablemutex);
    sprintf(reply, "This user login times : \"%d\"", n);
    Vdebug(DEBUG_DATE, "[admin]%s", reply);
    return 0;
}

int admin_sendmessage(char *reply, char *receive)
{
    struct table_chain *table;
    int n = 0;
    char name[USER_NAME_LENGTH + 1] = {0}, sessionid[SESSION_TX_LENGTH + 1] = {0};
    char message[UDP_PACKET_MAX_MESSAGE_LENGTH] = {0};
    int message_length;
    int checkname, checkid;

    checkname = (copy_message(name, USER_NAME_LENGTH + 1, receive, '"') > 0) ? 1 : 0;
    checkid = (copy_message(sessionid, SESSION_TX_LENGTH + 1, receive, '\'') > 0) ? 1 : 0;

    message_length = copy_message(message, sizeof(message), receive, '|');

    if ((!checkname) && (!checkid))
    {
        sprintf(reply, "Error sendmessage format, use \" to enclose the user name and ' to enclose session id.");
        Vdebug(DEBUG_DATE, "[admin]%s", reply);
        return 0;
    }
    if ((message_length == -1) || (message_length == 0))
    {
        sprintf(reply, "No message in sendmessage.");
        Vdebug(DEBUG_DATE, "[admin]%s", reply);
        return 0;
    }
    pthread_mutex_lock(&tablemutex);
    for (table = acct_table_header; table; table = table->next)
    {
        if (((checkname && strncmp(table->table->name, name, MAX(strlen(table->table->name), strlen(name)))) == 0)
                && ((checkid && strncmp(table->table->session_tx, sessionid, MAX(strlen(table->table->session_tx), strlen(sessionid)))) == 0))
        {
            table->table->set_message_length = message_length;
            memcpy(table->table->set_message, message, message_length);
            n++;
        }
    }
    pthread_mutex_unlock(&tablemutex);
    if (n == 0)
    {
        sprintf(reply, "No user found");
    }
    else
        sprintf(reply, "Send message %s", message);
    Vdebug(DEBUG_DATE, "[admin]%s", reply);
    return 0;
}

int admin_kickuser(char *reply, char *receive)
{
    struct table_chain *table;
    int n = 0;
    char name[USER_NAME_LENGTH + 1] = {0}, sessionid[SESSION_TX_LENGTH + 1] = {0};
    char message[UDP_PACKET_MAX_MESSAGE_LENGTH] = {0};
    int message_length;
    int checkname, checkid;

    checkname = (copy_message(name, USER_NAME_LENGTH + 1, receive, '"') > 0) ? 1 : 0;
    checkid = (copy_message(sessionid, SESSION_TX_LENGTH + 1, receive, '\'') > 0) ? 1 : 0;

    message_length = copy_message(message, sizeof(message), receive, '|');

    if ((!checkname) && (!checkid))
    {
        sprintf(reply, "Error kickuser format, use \" to enclose the user name and ' to enclose session id.");
        Vdebug(DEBUG_DATE, "[admin]%s", reply);
        return 0;
    }
    pthread_mutex_lock(&tablemutex);
	//MLog("Lock: admin kick user");
    for (table = acct_table_header; table; table = table->next)
    {
        //assert(table->table);
        if (((checkname && strncmp(table->table->name, name, MAX(strlen(table->table->name), strlen(name)))) == 0)
                && ((checkid && strncmp(table->table->session_tx, sessionid, MAX(strlen(table->table->session_tx), strlen(sessionid)))) == 0)
				&& (table->table->status == USER_ACCOUNTING))
        {
            table->table->update_time = time(NULL);
            table->table->status = USER_KICKOFF;
            if (message_length > 0)
            {
                if (strstr(message, "$2") == message)
                {
                    table->table->set_message_length = strlen(SERVER_MESSAGE_2);
                    memcpy(table->table->set_message, SERVER_MESSAGE_2, message_length);

                }
                else
                {
                    table->table->set_message_length = message_length;
                    memcpy(table->table->set_message, message, message_length);
                }
            }
            //table_chain[id].table->packet_id++;
            //generate_server_reply(SERVER_EXIT, id, reply);
            if (strstr(message, "$2") == message)
            {
                rad_account_packet_send(table, ACCT_STOP, "Host-Request", NULL);
            }
            else
                rad_account_packet_send(table, ACCT_STOP, "Admin-Reset", NULL);
            n++;
        }
    }
    pthread_mutex_unlock(&tablemutex);
	//MLog("UnLock: admin kick user");
    sprintf(reply, "Kick user number : \"%d\"", n);
    Vdebug(DEBUG_DATE, "[admin]%s", reply);
    return 0;
}

int admin_setrate(char *reply, char *receive)
{
    struct table_chain *table;
    int n = 0;
    char name[USER_NAME_LENGTH + 1] = {0}, sessionid[SESSION_TX_LENGTH + 1] = {0};
    char up[10] = {0}, down[10] = {0};
    unsigned int up_rate = 0, down_rate = 0;
    int len;
    int checkname, checkid;

    checkname = (copy_message(name, USER_NAME_LENGTH + 1, receive, '"') > 0) ? 1 : 0;
    checkid = (copy_message(sessionid, SESSION_TX_LENGTH + 1, receive, '\'') > 0) ? 1 : 0;

    if ((!checkname) && (!checkid))
    {
        sprintf(reply, "Error setrate format, use \" to enclose the user name and ' to enclose session id.");
        Vdebug(DEBUG_DATE, "[admin]%s", reply);
        return 0;
    }

    len = copy_message(up, sizeof(up), receive, '^');
    if (len > 0)
    {
        up_rate = atoi(up);
    }
    len = copy_message(down, sizeof(down), receive, '*');
    if (len > 0)
    {
        down_rate = atoi(down);
    }

    if ((up_rate == 0) && (down_rate == 0))
    {
        sprintf(reply, "Error setrate format, both rate are zero.");
        Vdebug(DEBUG_DATE, "[admin]%s", reply);
        return 0;
    }
    pthread_mutex_lock(&tablemutex);
    for (table = acct_table_header; table; table = table->next)
    {
        //assert(table->table);
        if (((checkname && strncmp(table->table->name, name, MAX(strlen(table->table->name), strlen(name)))) == 0)
                && ((checkid && strncmp(table->table->session_tx, sessionid, MAX(strlen(table->table->session_tx), strlen(sessionid)))) == 0))
        {
            table->table->set_download_rate = down_rate;
            table->table->set_upload_rate = up_rate;
            //table_chain[id].table->packet_id++;
            //generate_server_reply(SERVER_EXIT, id, reply);
            n++;
        }
    }
    pthread_mutex_unlock(&tablemutex);
    if (n == 0)
    {
        sprintf(reply, "No user found");
    }
    else
        sprintf(reply, "Set rate for %d user", n);
    Vdebug(DEBUG_DATE, "[admin]%s", reply);
    return 0;
}

#if USE_AES_AUTHENTICATION
struct aes_ctx *
aes_init()
{
    gen_tabs();

    if (aes_set_key(&aes,  (unsigned char*)AES_KEY , 16) != 0)
    {
        Vdebug(DEBUG_MAIN, "[main process]can't set key : %s " , AES_KEY );
        return 0;
    }
    return &aes;
}


int AES_accept(int *sock)
{
    int p;
    char receive[512] = {0};
    if ((p = AES_read(sock, receive, 512)) <= 0)
    {
        Vdebug(DEBUG_BOTH, "[AES accept]error accept client");
        return -1;
    }
    if (strncmp(receive, CLIENT_HELLO_MESSAGE, strlen(CLIENT_HELLO_MESSAGE)) != 0)
    {		
		if (strncmp(receive, CLIENT_KEEPALIVE_TCP, strlen(CLIENT_KEEPALIVE_TCP)) == 0)
		{
			Vdebug(DEBUG_DATE, "[AES accept]client use tcp keepalive\n");
			return 2;
		}
        Vdebug(DEBUG_BOTH, "[AES accept]error client packet, maybe the client's aes key is wrong\n");
        return -1;
    }
    if (AES_write(sock, "hello", 6) <= 0)
    {
        return -1;
    }
    return 1;
}

int AES_read(int *sock, char *buffer, int size)
{
    int p, loop;
    unsigned char temp[16] = {0};
    p = recv((unsigned long)sock, buffer, size, 0);
    if ((p == -1) || (p == 0))
    {
        return p;
    }
    if (p == size)
    {
        Vdebug(DEBUG_BOTH, "[AES read]warning, receive buffer exceeded, please set larger buffer size");
    }

    if (!AES_align_size(p))
    {
        Vdebug(DEBUG_BOTH, "[AES read]warning, client send error packet size %d", p);
        return -1;
    }

    for (loop = 0; loop < p / 16; loop++)
    {
        memset(temp , 0, 16);
        aes_decrypt(&aes, temp, (unsigned char*)buffer + loop * 16);
        memcpy(buffer + loop * 16, temp, 16);
    }
    return p;
}

int AES_write(int *sock, char *buffer, int size)
{
    int p, align_size, loop;
    unsigned char *temp, last[16] = {0};
    align_size = AES_pad_align_size(size);
    temp = malloc(align_size);
    if (!temp)
    {
        Vdebug(DEBUG_BOTH, "[AES write]error AES_write malloc");
        return -1;
    }
    memset(temp , 0, align_size);
    for (loop = 0; loop < align_size / 16; loop++)
    {
        if (loop == align_size / 16 - 1)       //last aes block , padding 0 to match 16 bytes
        {
            memset(last, 0 ,16);
            memcpy(last, buffer + loop * 16, size - loop * 16);
            aes_encrypt(&aes, temp + loop * 16, last);
        }
        else aes_encrypt(&aes, temp + loop * 16, (unsigned char *) buffer + loop * 16);
    }
    p = send((unsigned long)sock, temp, align_size, 0);
    if (p == -1)
    {
        Vdebug(DEBUG_BOTH, "[AES write]error send AES packet");
    }
    free(temp);
    return p;
}
#endif
